#include "MinionBehavior.h"
#include "SetDestinationEvent.h"
#include "DestinationReachedEvent.h"
#include "AddDestinationEvent.h"
#include "GameApplication.h"
#include "GlobalTimer.h"
#include "TargettedAbility.h"
#include "CombatSystem.h"
#include "CombatRequest.h"

#define SECONDS_BETWEEN_MOVE_UPDATE 0.5f
#define SECONDS_BETWEEN_PATH_UPDATE 10.0f

const HashedString MinionBehavior::type("MinionBehavior");

const HashedString& MinionBehavior::Type() const
{
	return type;
}

MinionBehavior::MinionBehavior( AIEntityController* controller ) : IBehavior(controller), m_engagedEnemy(0), m_state(DEFAULT)
{
	Maths::Vector3D destination;

	m_controller->getPosition(destination);
	m_controller->UpdateDestination(destination);
	m_controller->UpdatePath();
}

bool MinionBehavior::Destination( IEvent& event )
{
	if (event.Type() == SetDestinationEvent::type)
	{
		SetDestinationEvent& setDestination = static_cast<SetDestinationEvent&>(event);
		
		Maths::Vector3D destination = setDestination.Destination();

		m_controller->UpdateDestination(destination);
		m_controller->UpdatePath();	
	}
	return false;
}

bool MinionBehavior::DestinationReached( IEvent& event )
{
	if (event.Type() == DestinationReachedEvent::type)
	{
		std::list<Maths::Vector3D>& waypoints = m_controller->getWaypoints();
		if (waypoints.size() > 0)
		{
			Maths::Vector3D next = waypoints.front();
			waypoints.pop_front();

			m_controller->ReceiveEvent(SetDestinationEvent(next));
		} else
		{
			Maths::Vector3D position;
			m_controller->getPosition(position);
			m_controller->ReceiveEvent(SetDestinationEvent(position));
		}
	}
	return false;
}

bool MinionBehavior::AddDestination( IEvent& event )
{
	if (event.Type() == AddDestinationEvent::type)
	{
		AddDestinationEvent& addDestination = static_cast<AddDestinationEvent&>(event);
		Maths::Vector3D destination = addDestination.Destination();

		std::list<Maths::Vector3D>& waypoints = m_controller->getWaypoints();
		waypoints.push_back(destination);
	}
	return false;
}

inline static float getDistanceBetween( Entity* entity, Maths::Vector3D& position )
{
	OrientationComponent* orientation = (OrientationComponent*)((entity)->FindComponent(OrientationComponent::type));
	Maths::Vector3D entityPosition = orientation->getPosition().getObject();

	return position.DistanceSqr(entityPosition);
}

HashedString MinionBehavior::EnemyGroup()
{
	return m_enemyGroup;
}
void MinionBehavior::EnemyGroup( HashedString name )
{
	m_enemyGroup = name;
}

Entity* MinionBehavior::FindClosestEnemy()
{	
	Entity* enemy = 0;
	OrientationComponent* orientation = 0;
	Maths::Vector3D position;
	float distance;
	float closestDistance = FLT_MAX;

	m_controller->getPosition(position);
	std::list<Entity*>& enemies = Game.getEntityGroup(m_enemyGroup);

	for (std::list<Entity*>::iterator it = enemies.begin(); it != enemies.end(); ++it)
	{
		orientation = (OrientationComponent*)((*it)->FindComponent(OrientationComponent::type));
		Maths::Vector3D enemyPosition = orientation->getPosition().getObject();

		distance = position.DistanceSqr(enemyPosition);

		if (distance < closestDistance)
		{
			closestDistance = distance;
			enemy = (*it);
		}
	}
	return enemy;
}	

void MinionBehavior::Update()
{
	// Time to update?
	float secondsSinceStart = GlobalTimer::getTime().getSecondsSinceStart();

	switch (m_state)
	{
	case DEFAULT:
		{
			if (secondsSinceStart > (m_lastMoveTime.getSecondsSinceStart() + SECONDS_BETWEEN_MOVE_UPDATE) && !(m_controller->Vehicle().Sleeping()))
			{
				int speed = 0;
				Maths::Vector3D direction;

				m_controller->getStatus("Speed", speed);							// retreive speed
				m_controller->getDirection(direction);								// retreive direction

				if (direction == Maths::Vector3D::ZERO && m_controller->getWaypoints().size() > 0)
					m_controller->ReceiveEvent(DestinationReachedEvent());

				m_controller->UpdateVelocity(direction * (float)speed);				// set new velocity

				m_lastMoveTime = GlobalTimer::getTime();
			}
			
			// Time to update?
			if (secondsSinceStart > (m_lastPathTime.getSecondsSinceStart() + SECONDS_BETWEEN_PATH_UPDATE))
			{
				m_controller->UpdatePath();
				m_lastPathTime = GlobalTimer::getTime();
			}
			
			Entity* enemy = FindClosestEnemy();

			if (enemy)
			{
				int aggressionRange = 10;
				m_controller->getStatus("AggressionRange", aggressionRange);
				Maths::Vector3D position;
				m_controller->getPosition(position);

				float distance = getDistanceBetween(enemy, position);

				if (distance < aggressionRange * aggressionRange)
				{
					m_state = ATTACKING;
					m_engagedEnemy = enemy;
				}
			}

			break;
		}
	case ATTACKING:
		{
			if (!Game.EntityExists(m_engagedEnemy))
			{
				m_state = DEFAULT;
				m_engagedEnemy = 0;
				return;
			}

			int attackSpeed = 100;
			m_controller->getStatus("AttackSpeed", attackSpeed);
			float actualAttackSpeed = (float)attackSpeed/100.0f;
			Maths::Vector3D position;
			m_controller->getPosition(position);

			float distance = getDistanceBetween(m_engagedEnemy, position);

			Combat::IAbility::Vector_t& abilities = m_controller->getAbilities();

			Combat::TargettedAbility* actAbility = (Combat::TargettedAbility*)((Combat::IAbility*)abilities[0]);
			float range = actAbility->Range();

			if (distance <= range * range)
			{
				if (secondsSinceStart > m_lastAutoAttack.getSecondsSinceStart() + actualAttackSpeed)
				{
					DoAutoAttack();
				}
				m_controller->UpdateVelocity(Maths::Vector3D(Maths::Vector3D::ZERO));				// set new velocity
			} else
			{
				int speed = 0;
				OrientationComponent* orientation = (OrientationComponent*)((m_engagedEnemy)->FindComponent(OrientationComponent::type));
				Maths::Vector3D entityPosition = orientation->getPosition().getObject();
				
				m_controller->getStatus("Speed", speed);							// retreive speed
				Maths::Vector3D direction = (entityPosition - position).Normal();

				m_controller->UpdateVelocity(direction * (float)speed);				// set new velocity
			}
			break;
		}
	}
}

void MinionBehavior::DoAutoAttack()
{
	CombatComponent* target = (CombatComponent*)(m_engagedEnemy->FindComponent(CombatComponent::type));
	CombatComponent* me = (CombatComponent*)(m_controller->getEntity()->FindComponent(CombatComponent::type));

	if (target && me)
	{
		Combat::TargettedRequest* targettedRequest;
		Pointer<Combat::Request> request = targettedRequest = new Combat::TargettedRequest();

		targettedRequest->ability = 0;
		targettedRequest->requester = me->Combatant();
		targettedRequest->target = target->Combatant();

		CombatSystem.SubmitRequest(request);

		m_lastAutoAttack = GlobalTimer::getTime();
	}
}

bool MinionBehavior::ToXML( tinyxml2::XMLPrinter& printer )
{
	return true;
}

bool MinionBehavior::FromXML( tinyxml2::XMLElement* element )
{
	return true;
}